<!-- 烟花效果 | 礼花效果 -->
<template>
  <canvas ref="canvasRef" class="layout-fireworks"></canvas>
</template>

<script setup lang="ts">
  import { useEventListener } from '@vueuse/core'
  import { mittBus } from '@/utils/sys'
  import type { Handler } from 'mitt'
  import bp from '@/assets/img/ceremony/hb.png'
  import sd from '@/assets/img/ceremony/sd.png'
  import yd from '@/assets/img/ceremony/yd.png'

  defineOptions({ name: 'ArtFireworksEffect' })

  /**
   * 烟花系统配置接口
   * 定义了烟花效果的所有可配置参数
   */
  interface FireworkConfig {
    /** 对象池大小 - 预先创建的粒子对象数量 */
    readonly POOL_SIZE: number
    /** 每次爆炸产生的粒子数量 */
    readonly PARTICLES_PER_BURST: number
    /** 各种形状的尺寸配置 */
    readonly SIZES: {
      /** 矩形粒子的宽高 */
      readonly RECTANGLE: { readonly WIDTH: number; readonly HEIGHT: number }
      /** 正方形粒子的边长 */
      readonly SQUARE: { readonly SIZE: number }
      /** 圆形粒子的直径 */
      readonly CIRCLE: { readonly SIZE: number }
      /** 三角形粒子的边长 */
      readonly TRIANGLE: { readonly SIZE: number }
      /** 椭圆粒子的宽高 */
      readonly OVAL: { readonly WIDTH: number; readonly HEIGHT: number }
      /** 图片粒子的宽高 */
      readonly IMAGE: { readonly WIDTH: number; readonly HEIGHT: number }
    }
    /** 旋转相关参数 */
    readonly ROTATION: {
      /** 基础旋转速度 */
      readonly BASE_SPEED: number
      /** 随机旋转速度增量 */
      readonly RANDOM_SPEED: number
      /** 旋转衰减系数 - 控制旋转速度递减 */
      readonly DECAY: number
    }
    /** 物理效果参数 */
    readonly PHYSICS: {
      /** 重力加速度 */
      readonly GRAVITY: number
      /** 下落速度阈值 - 超过此值开始淡出 */
      readonly VELOCITY_THRESHOLD: number
      /** 透明度衰减速度 */
      readonly OPACITY_DECAY: number
    }
    /** 烟花粒子颜色配置(带透明度) */
    readonly COLORS: readonly string[]
    /** 烟花粒子形状配置(矩形出现概率更高) */
    readonly SHAPES: readonly string[]
  }

  /**
   * 单个烟花粒子对象
   * 包含粒子的位置、速度、外观等所有属性
   */
  interface Firework {
    /** X坐标位置 */
    x: number
    /** Y坐标位置 */
    y: number
    /** X方向速度 */
    vx: number
    /** Y方向速度 */
    vy: number
    /** 粒子颜色 (RGBA格式) */
    color: string
    /** 当前旋转角度 */
    rotation: number
    /** 旋转速度 */
    rotationSpeed: number
    /** 缩放比例 */
    scale: number
    /** 粒子形状类型 */
    shape: string
    /** 透明度 (0-1) */
    opacity: number
    /** 是否处于活动状态 */
    active: boolean
    /** 图片URL (当shape为image时使用) */
    imageUrl?: string
  }

  /**
   * 图片缓存接口
   * 用于缓存预加载的图片资源
   */
  interface ImageCache {
    [url: string]: HTMLImageElement
  }

  // ==================== 配置常量 ====================

  /**
   * 烟花效果的全局配置
   * 使用 as const 确保配置的不可变性
   */
  const CONFIG: FireworkConfig = {
    // 性能相关配置
    POOL_SIZE: 600, // 对象池大小，影响同时存在的最大粒子数
    PARTICLES_PER_BURST: 200, // 每次爆炸的粒子数量，影响视觉效果密度

    // 粒子尺寸配置
    SIZES: {
      RECTANGLE: { WIDTH: 24, HEIGHT: 12 }, // 矩形粒子尺寸
      SQUARE: { SIZE: 12 }, // 正方形粒子尺寸
      CIRCLE: { SIZE: 12 }, // 圆形粒子尺寸
      TRIANGLE: { SIZE: 10 }, // 三角形粒子尺寸
      OVAL: { WIDTH: 24, HEIGHT: 12 }, // 椭圆粒子尺寸
      IMAGE: { WIDTH: 30, HEIGHT: 30 } // 图片粒子尺寸
    },

    // 旋转动画配置
    ROTATION: {
      BASE_SPEED: 2, // 基础旋转速度
      RANDOM_SPEED: 3, // 额外随机旋转速度范围
      DECAY: 0.98 // 旋转速度衰减系数 (越小衰减越快)
    },

    // 物理效果配置
    PHYSICS: {
      GRAVITY: 0.525, // 重力加速度，影响粒子下落速度
      VELOCITY_THRESHOLD: 10, // 速度阈值，超过时开始透明度衰减
      OPACITY_DECAY: 0.02 // 透明度衰减速度，影响粒子消失快慢
    },

    // 粒子颜色配置 - 使用RGBA格式支持透明度
    COLORS: [
      'rgba(255, 68, 68, 1)', // 红色系
      'rgba(255, 68, 68, 0.9)',
      'rgba(255, 68, 68, 0.8)',
      'rgba(255, 116, 188, 1)', // 粉色系
      'rgba(255, 116, 188, 0.9)',
      'rgba(255, 116, 188, 0.8)',
      'rgba(68, 68, 255, 0.8)', // 蓝色系
      'rgba(92, 202, 56, 0.7)', // 绿色系
      'rgba(255, 68, 255, 0.8)', // 紫色系
      'rgba(68, 255, 255, 0.7)', // 青色系
      'rgba(255, 136, 68, 0.7)', // 橙色系
      'rgba(68, 136, 255, 1)', // 蓝色系
      'rgba(250, 198, 122, 0.8)' // 金色系
    ],

    // 粒子形状配置 - 矩形出现概率更高，营造更丰富的视觉效果
    SHAPES: [
      'rectangle',
      'rectangle',
      'rectangle',
      'rectangle',
      'rectangle',
      'rectangle',
      'rectangle',
      'circle',
      'triangle',
      'oval'
    ]
  } as const

  // ==================== 响应式状态 ====================

  /** Canvas DOM 元素引用 */
  const canvasRef = ref<HTMLCanvasElement>()
  /** Canvas 2D 绘制上下文 */
  const ctx = ref<CanvasRenderingContext2D | null>(null)

  // ==================== 烟花系统 ====================

  /**
   * 烟花系统核心类
   * 负责管理粒子生命周期、渲染和动画
   */
  class FireworkSystem {
    /** 粒子对象池 - 预先创建的粒子对象数组 */
    private particlePool: Firework[] = []
    /** 当前活动的粒子数组 */
    private activeParticles: Firework[] = []
    /** 对象池索引指针 - 用于循环分配粒子 */
    private poolIndex = 0
    /** 图片资源缓存 */
    private imageCache: ImageCache = {}
    /** 动画帧ID - 用于取消动画 */
    private animationId = 0
    /** 画布宽度缓存 */
    private canvasWidth = 0
    /** 画布高度缓存 */
    private canvasHeight = 0

    constructor() {
      this.initializePool()
    }

    /**
     * 初始化对象池
     * 预先创建指定数量的粒子对象，避免运行时频繁创建
     */
    private initializePool(): void {
      for (let i = 0; i < CONFIG.POOL_SIZE; i++) {
        this.particlePool.push(this.createParticle())
      }
    }

    /**
     * 创建一个新的粒子对象
     * 返回初始化状态的粒子
     */
    private createParticle(): Firework {
      return {
        x: 0,
        y: 0,
        vx: 0,
        vy: 0,
        color: '',
        rotation: 0,
        rotationSpeed: 0,
        scale: 1,
        shape: 'circle',
        opacity: 1,
        active: false
      }
    }

    /**
     * 从对象池获取可用粒子 (性能优化版本)
     * 使用循环索引而非Array.find()，时间复杂度从O(n)降至O(1)
     * @returns 可用的粒子对象或null
     */
    private getAvailableParticle(): Firework | null {
      for (let i = 0; i < CONFIG.POOL_SIZE; i++) {
        const index = (this.poolIndex + i) % CONFIG.POOL_SIZE
        const particle = this.particlePool[index]

        if (!particle.active) {
          this.poolIndex = (index + 1) % CONFIG.POOL_SIZE
          particle.active = true
          return particle
        }
      }
      return null
    }

    /**
     * 预加载单个图片资源
     * @param url 图片URL
     * @returns Promise<HTMLImageElement>
     */
    async preloadImage(url: string): Promise<HTMLImageElement> {
      // 如果已缓存，直接返回
      if (this.imageCache[url]) {
        return this.imageCache[url]
      }

      return new Promise((resolve, reject) => {
        const img = new Image()
        img.crossOrigin = 'anonymous' // 处理跨域问题
        img.onload = () => {
          this.imageCache[url] = img
          resolve(img)
        }
        img.onerror = reject
        img.src = url
      })
    }

    /**
     * 预加载所有需要的图片资源
     * 在组件初始化时调用，确保图片ready
     */
    async preloadAllImages(): Promise<void> {
      const imageUrls = [bp, sd, yd]
      try {
        await Promise.all(imageUrls.map((url) => this.preloadImage(url)))
      } catch (error) {
        console.error('Image preloading failed:', error)
      }
    }

    /**
     * 创建烟花爆炸效果
     * @param imageUrl 可选的图片URL，如果提供则使用图片粒子
     */
    createFirework(imageUrl?: string): void {
      // 随机确定爆炸起始位置
      const startX = Math.random() * this.canvasWidth
      const startY = this.canvasHeight

      // 根据是否有图片确定可用形状
      const availableShapes = imageUrl && this.imageCache[imageUrl] ? ['image'] : CONFIG.SHAPES

      // 批量创建粒子数组，减少频繁的数组操作
      const particles: Firework[] = []

      for (let i = 0; i < CONFIG.PARTICLES_PER_BURST; i++) {
        const particle = this.getAvailableParticle()
        if (!particle) continue

        // 计算粒子发射角度和速度 (恢复原始算法)
        const angle = (Math.PI * i) / (CONFIG.PARTICLES_PER_BURST / 2) // 扇形分布
        const speed = (12 + Math.random() * 6) * 1.5 // 随机速度
        const spread = Math.random() * Math.PI * 2 // 360度随机扩散

        // 直接属性赋值，避免Object.assign的性能开销
        particle.x = startX
        particle.y = startY
        // 复杂的速度计算，模拟真实烟花爆炸轨迹
        particle.vx = Math.cos(angle) * Math.cos(spread) * speed * (Math.random() * 0.5 + 0.5)
        particle.vy = Math.sin(angle) * speed - 15 // 向上初始速度
        particle.color = CONFIG.COLORS[Math.floor(Math.random() * CONFIG.COLORS.length)]
        particle.rotation = Math.random() * 360
        particle.rotationSpeed =
          (Math.random() * CONFIG.ROTATION.RANDOM_SPEED + CONFIG.ROTATION.BASE_SPEED) *
          (Math.random() > 0.5 ? 1 : -1) // 随机旋转方向
        particle.scale = 0.8 + Math.random() * 0.4 // 随机缩放
        particle.shape = availableShapes[Math.floor(Math.random() * availableShapes.length)]
        particle.opacity = 1
        particle.imageUrl = imageUrl && this.imageCache[imageUrl] ? imageUrl : undefined

        particles.push(particle)
      }

      // 批量添加到活动粒子数组，减少多次数组操作
      this.activeParticles.push(...particles)
    }

    /**
     * 更新所有粒子的物理状态 (性能优化版本)
     * 包括位置、速度、旋转、透明度等
     */
    private updateParticles(): void {
      const { GRAVITY, VELOCITY_THRESHOLD, OPACITY_DECAY } = CONFIG.PHYSICS
      const { DECAY } = CONFIG.ROTATION

      // 使用倒序遍历，避免删除元素时的索引混乱问题
      for (let i = this.activeParticles.length - 1; i >= 0; i--) {
        const particle = this.activeParticles[i]

        // 更新粒子位置 (匀加速运动)
        particle.x += particle.vx
        particle.y += particle.vy
        particle.vy += GRAVITY // 重力影响

        // 更新旋转状态
        particle.rotation += particle.rotationSpeed
        particle.rotationSpeed *= DECAY // 旋转速度衰减

        // 透明度衰减逻辑 - 当粒子下落速度超过阈值时开始淡出
        if (particle.vy > VELOCITY_THRESHOLD) {
          particle.opacity -= OPACITY_DECAY
          if (particle.opacity <= 0) {
            this.recycleParticle(i)
            continue
          }
        }

        // 边界检查 - 移除超出屏幕范围的粒子
        if (this.isOutOfBounds(particle)) {
          this.recycleParticle(i)
        }
      }
    }

    /**
     * 回收粒子到对象池
     * @param index 要回收的粒子在活动数组中的索引
     */
    private recycleParticle(index: number): void {
      const particle = this.activeParticles[index]
      particle.active = false // 标记为非活动状态
      this.activeParticles.splice(index, 1) // 从活动数组中移除
    }

    /**
     * 检查粒子是否超出屏幕边界
     * @param particle 要检查的粒子
     * @returns 是否超出边界
     */
    private isOutOfBounds(particle: Firework): boolean {
      const margin = 100 // 边界缓冲区
      return (
        particle.x < -margin ||
        particle.x > this.canvasWidth + margin ||
        particle.y < -margin ||
        particle.y > this.canvasHeight + margin
      )
    }

    /**
     * 绘制单个粒子
     * @param particle 要绘制的粒子对象
     */
    private drawParticle(particle: Firework): void {
      if (!ctx.value) return

      // 保存当前画布状态
      ctx.value.save()
      ctx.value.globalAlpha = particle.opacity // 设置透明度
      ctx.value.translate(particle.x, particle.y) // 移动到粒子位置
      ctx.value.rotate((particle.rotation * Math.PI) / 180) // 应用旋转
      ctx.value.scale(particle.scale, particle.scale) // 应用缩放

      // 渲染粒子形状
      this.renderShape(particle)

      // 恢复画布状态
      ctx.value.restore()
    }

    /**
     * 根据粒子类型渲染对应的形状
     * @param particle 要渲染的粒子
     */
    private renderShape(particle: Firework): void {
      if (!ctx.value) return

      const { SIZES } = CONFIG
      ctx.value.fillStyle = particle.color

      switch (particle.shape) {
        case 'rectangle':
          // 绘制矩形
          ctx.value.fillRect(
            -SIZES.RECTANGLE.WIDTH / 2,
            -SIZES.RECTANGLE.HEIGHT / 2,
            SIZES.RECTANGLE.WIDTH,
            SIZES.RECTANGLE.HEIGHT
          )
          break

        case 'square':
          // 绘制正方形
          ctx.value.fillRect(
            -SIZES.SQUARE.SIZE / 2,
            -SIZES.SQUARE.SIZE / 2,
            SIZES.SQUARE.SIZE,
            SIZES.SQUARE.SIZE
          )
          break

        case 'circle':
          // 绘制圆形
          ctx.value.beginPath()
          ctx.value.arc(0, 0, SIZES.CIRCLE.SIZE / 2, 0, Math.PI * 2)
          ctx.value.fill()
          break

        case 'triangle':
          // 绘制三角形
          ctx.value.beginPath()
          ctx.value.moveTo(0, -SIZES.TRIANGLE.SIZE)
          ctx.value.lineTo(SIZES.TRIANGLE.SIZE, SIZES.TRIANGLE.SIZE)
          ctx.value.lineTo(-SIZES.TRIANGLE.SIZE, SIZES.TRIANGLE.SIZE)
          ctx.value.closePath()
          ctx.value.fill()
          break

        case 'oval':
          // 绘制椭圆
          ctx.value.beginPath()
          ctx.value.ellipse(0, 0, SIZES.OVAL.WIDTH / 2, SIZES.OVAL.HEIGHT / 2, 0, 0, Math.PI * 2)
          ctx.value.fill()
          break

        case 'image':
          // 绘制图片
          this.renderImage(particle)
          break
      }
    }

    /**
     * 渲染图片类型的粒子
     * @param particle 包含图片URL的粒子对象
     */
    private renderImage(particle: Firework): void {
      if (!ctx.value || !particle.imageUrl) return

      const img = this.imageCache[particle.imageUrl]
      if (img?.complete) {
        const { WIDTH, HEIGHT } = CONFIG.SIZES.IMAGE
        ctx.value.drawImage(img, -WIDTH / 2, -HEIGHT / 2, WIDTH, HEIGHT)
      }
    }

    /**
     * 渲染所有活动粒子到画布
     * 清除画布并重新绘制所有粒子
     */
    private render(): void {
      if (!ctx.value || !canvasRef.value) return

      // 清除整个画布
      ctx.value.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
      // 设置混合模式为"变亮"，增强视觉效果
      ctx.value.globalCompositeOperation = 'lighter'

      // 渲染所有活动粒子
      for (const particle of this.activeParticles) {
        this.drawParticle(particle)
      }
    }

    /**
     * 动画主循环
     * 使用箭头函数保持this绑定
     */
    private animate = (): void => {
      this.updateParticles() // 更新粒子状态
      this.render() // 渲染粒子
      this.animationId = requestAnimationFrame(this.animate) // 请求下一帧
    }

    /**
     * 更新画布尺寸缓存
     * 在窗口大小改变时调用
     * @param width 新的画布宽度
     * @param height 新的画布高度
     */
    updateCanvasSize(width: number, height: number): void {
      this.canvasWidth = width
      this.canvasHeight = height
    }

    /**
     * 启动动画循环
     */
    start(): void {
      this.animate()
    }

    /**
     * 停止动画循环
     * 在组件卸载时调用，避免内存泄漏
     */
    stop(): void {
      if (this.animationId) {
        cancelAnimationFrame(this.animationId)
        this.animationId = 0
      }
    }

    /**
     * 获取当前活动粒子数量
     * 用于调试和性能监控
     * @returns 活动粒子数量
     */
    getActiveParticleCount(): number {
      return this.activeParticles.length
    }
  }

  // ==================== 组件逻辑 ====================

  /** 烟花系统实例 */
  const fireworkSystem = new FireworkSystem()

  /**
   * 处理键盘快捷键
   * 监听 Ctrl+Shift+P 或 Cmd+Shift+P 组合键触发烟花
   * @param event 键盘事件对象
   */
  const handleKeyPress = (event: KeyboardEvent): void => {
    const isFireworkShortcut =
      (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 'p') ||
      (event.metaKey && event.shiftKey && event.key.toLowerCase() === 'p')

    if (isFireworkShortcut) {
      event.preventDefault()
      fireworkSystem.createFirework()
    }
  }

  /**
   * 调整Canvas画布大小
   * 响应窗口大小变化，确保画布始终覆盖整个视口
   */
  const resizeCanvas = (): void => {
    if (!canvasRef.value) return

    const { innerWidth, innerHeight } = window
    canvasRef.value.width = innerWidth
    canvasRef.value.height = innerHeight
    fireworkSystem.updateCanvasSize(innerWidth, innerHeight)
  }

  /**
   * 处理外部触发的烟花事件
   * 通过 mittBus 事件总线接收触发指令
   * @param event 事件数据，可能包含图片URL
   */
  const handleFireworkTrigger: Handler<unknown> = (event: unknown) => {
    const imageUrl = event as string | undefined
    fireworkSystem.createFirework(imageUrl)
  }

  // ==================== 生命周期 ====================

  /**
   * 组件挂载时的初始化逻辑
   */
  onMounted(async () => {
    if (!canvasRef.value) return

    // 获取Canvas 2D绘制上下文
    ctx.value = canvasRef.value.getContext('2d')
    if (!ctx.value) return

    // 设置初始画布大小
    resizeCanvas()

    // 预加载所有图片资源
    await fireworkSystem.preloadAllImages()

    // 启动动画循环
    fireworkSystem.start()

    // 注册事件监听器
    useEventListener(window, 'keydown', handleKeyPress) // 键盘快捷键
    useEventListener(window, 'resize', resizeCanvas) // 窗口大小变化
    mittBus.on('triggerFireworks', handleFireworkTrigger) // 外部触发事件
  })

  /**
   * 组件卸载时的清理逻辑
   * 停止动画循环并移除事件监听器，防止内存泄漏
   */
  onUnmounted(() => {
    fireworkSystem.stop()
    mittBus.off('triggerFireworks', handleFireworkTrigger)
  })
</script>

<style scoped>
  /**
 * 烟花画布样式
 * 固定定位覆盖整个视口，不响应鼠标事件
 */
  .layout-fireworks {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 9999; /* 最高层级，确保在所有元素之上 */
    width: 100%;
    height: 100%;
    pointer-events: none; /* 不阻挡用户交互 */
  }
</style>
